﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Web;

/* INFO
 * 
 * Class got at http://www.blackbeltcoder.com/Articles/strings/generating-and-parsing-roman-numerals
 * 
 */

namespace borkoSi.Addons.Classes
{
    public class RomanNumerals
    {
        private class RomanInfo
        {
            public int Value { get; set; }
            public string Numerals { get; set; }
        }

        private static List<RomanInfo> _lookup;

        // Construction
        static RomanNumerals()
        {
            _lookup = new List<RomanInfo>();
            _lookup.Add(new RomanInfo() { Value = 1000, Numerals = "M" });
            _lookup.Add(new RomanInfo() { Value = 900, Numerals = "CM" });
            _lookup.Add(new RomanInfo() { Value = 500, Numerals = "D" });
            _lookup.Add(new RomanInfo() { Value = 400, Numerals = "CD" });
            _lookup.Add(new RomanInfo() { Value = 100, Numerals = "C" });
            _lookup.Add(new RomanInfo() { Value = 90, Numerals = "XC" });
            _lookup.Add(new RomanInfo() { Value = 50, Numerals = "L" });
            _lookup.Add(new RomanInfo() { Value = 40, Numerals = "XL" });
            _lookup.Add(new RomanInfo() { Value = 10, Numerals = "X" });
            _lookup.Add(new RomanInfo() { Value = 9, Numerals = "IX" });
            _lookup.Add(new RomanInfo() { Value = 5, Numerals = "V" });
            _lookup.Add(new RomanInfo() { Value = 4, Numerals = "IV" });
            _lookup.Add(new RomanInfo() { Value = 1, Numerals = "I" });
        }

        /// <summary>
        /// Converts an integer value to the equivalent string of
        /// Roman numerals.
        /// </summary>
        /// <param name="value">Value to convert</param>
        public static string ToString(int value)
        {
            // Although Roman numerals don't generally include
            // zero, we'll use 'N' (for the Latin word nulla)
            // if necessary
            if(value == 0)
                return "N";

            // Otherwise, use lookup table to build string
            StringBuilder builder = new StringBuilder();
            foreach(RomanInfo info in _lookup)
            {
                while(value >= info.Value)
                {
                    builder.Append(info.Numerals);
                    value -= info.Value;
                }
            }
            return builder.ToString();
        }

        /// <summary>
        /// Converts a string of Roman numeral characters to the
        /// equivalent integer value.
        /// </summary>
        /// <param name="s">String to convert</param>
        public static int Parse(string s)
        {
            int result;
            if(!TryParse(s, out result))
                throw new ArgumentException();
            return result;
        }

        /// <summary>
        /// Attempts to convert a string of Roman numeral characters to
        /// the equivalent integer value.
        /// </summary>
        /// <param name="s">String to conver</param>
        /// <param name="result">The result of the conversion</param>
        public static bool TryParse(string s, out int result)
        {
            // Normalize input
            s = s.Trim().ToUpper();

            int pos = 0;
            result = 0;

            // Special handling for zero
            if(s[pos] == 'N')
                pos++;

            // Use lookup table to parse string
            while(pos < s.Length && !Char.IsWhiteSpace(s[pos]))
            {
                RomanInfo info;

                // Test for double-character match
                if((s.Length - pos) >= 2)
                {
                    string pair = s.Substring(pos, 2);
                    info = _lookup.Find(i => i.Numerals == pair);
                    if(info != null)
                    {
                        result += info.Value;
                        pos += 2;
                        continue;
                    }
                }

                // Otherwise, text for single-character match
                string letter = s.Substring(pos, 1);
                info = _lookup.Find(i => i.Numerals == letter);
                if(info != null)
                {
                    result += info.Value;
                    pos++;
                }
                else
                {
                    // Invalid character encountered
                    return false;
                }
            }
            // Return true if any characters processed
            return (pos > 0);
        }
    }
}